package gbench.sandbox.record;

import org.junit.jupiter.api.Test;

import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import gbench.common.matlib.MatlibCanvas.XVec;
import gbench.common.matlib.data.DataReader.DFrame;
import gbench.common.tree.LittleTree;
import gbench.common.tree.LittleTree.AbstractMapRecord2;
import gbench.common.tree.LittleTree.IRecord;

import java.util.Optional;

import static gbench.common.matlib.MatlibCanvas.println;
import static gbench.common.tree.LittleTree.IRecord.*;

/**
 * State Data
 * 
 * @author xuqinghua
 *
 */
class State extends AbstractMapRecord2<State> {

    public State(final IRecord data) {
        super.data = data.toLhm();
    }

    public State() {
        this(REC());
    }

    @Override
    public State duplicate2() {
        return new State(REC(this.data));
    }

    /**
     * default key value is 0d
     * 
     * @param key
     * @param d
     * @return
     */
    public Double plus(final String key, final Number defaultValue, final Double d) {
        return this.duplicate().compute(key, (Double _d) -> Optional.ofNullable(_d).map(p -> p + d).orElse(d));
    }

    /**
     * default key value is 0d
     * 
     * @param key
     * @param d
     * @return
     */
    public Double plus(final String key, final Double d) {
        return this.plus(key, 0d, d);
    }

    /**
     * 
     * @param key
     * @param d
     * @return
     */
    public Double multiply(final String key, final Double defaultValue, final Double d) {
        return this.dbl(key, defaultValue) * d;
    }

    /**
     * 
     * @param key
     * @param d
     * @return
     */
    public Double multiply(final String key, final Double d) {
        return this.multiply(key, 1d, d);
    }
    
    /**
     * 
     * @param statement
     * @return Object
     */
    public Object evaluate(final String statement) {

        final var binding = new Binding();
        this.forEach(p -> binding.setVariable(p.key(), p.value()));
        final var shell = new GroovyShell(binding);
        final Object ret = LittleTree.trycatch((String s) -> shell.evaluate(s))
            .apply(statement);
        
        return ret;
    }
    
    /**
     * 
     * @param rec
     * @return
     */
    public static State of(IRecord rec) {
        return new State(rec);
    }
    
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
}


/**
 * 
 * @author xuqinghua
 *
 */
public class JunitStateRecord {
    
    @Test
    public void foo() {
        final var rb = IRecord.builder(
            "date,expression,value"); // header
        final var entries = XVec.OF ( // entries
            2007,    "200*(1-20%)*100*18*1/3",          "96000",
            2008,    "200*(1-15%)*100*18*2/3-96000",    "108000",
            2009,    "155*100*18-204000",               "75000"
        ).sliceS(rb::get,3); // [rec]
        final var prev = new State(); // previous line
        final var dfm = entries
            .map(e -> e.derive("total", prev.plus("total",e.dbl("value"))))
            .map(prev::update2) // update state
            .collect(DFrame.dfmclc);
        
        println(dfm);
    }
    
    /**
     * 
     */
    @Test
    public void cashflow() {
        final var rate = 0.1;
        final var cf = IRecord.builder(
            "date,amount"); // header
        final var prev = new State();
        final XVec<IRecord> cashflowX = XVec.OF ( // entries
                2007,    96000,
                2008,    108000,
                2009,    75000
            ).sliceX(cf::get,2); // cashflow
        final var dfm = cashflowX
            .fmap((i, e) -> e.set("npr", cashflowX.size() - i) // numer of period
            ).map(e -> e.set("pv", State.of(e.add("rate",rate)) // present value
                .evaluate("amount/Math.pow(1+rate,npr)")) // present value formula
            ).map(e -> e.set("total", prev.plus("total", e.dbl("pv"))) // total value
            ).map(prev::update2) // update previous
            .collect(DFrame.dfmclc);
        
        println(dfm);
    }
    
}
